package com.zeyu.framework.utils.mybatis.plugin;

import com.google.common.collect.ImmutableSet;
import com.sun.source.util.Trees;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.util.Context;
import com.zeyu.framework.utils.mybatis.annotation.DaoAutoGenerate;
import com.zeyu.framework.utils.mybatis.generate.DaoEnvironment;
import com.zeyu.framework.utils.mybatis.generate.DaoGenerateHelper;
import com.zeyu.framework.utils.mybatis.generate.MapperMethod;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.TypeElement;
import javax.tools.StandardLocation;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 自动生成的执行器
 * Created by zeyuphoenix on 2016/11/2.
 */
@SupportedAnnotationTypes("com.zeyu.framework.utils.mybatis.annotation.DaoAutoGenerate")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DaoAutoGenerateProcessor extends AbstractProcessor {

    // ================================================================
    // Constants
    // ================================================================

    /**
     * 路径
     */
    public static final String PATH = DaoAutoGenerate.class.getCanonicalName();

    /**
     * 忽略的方法
     */
    private static Set<String> methodsInObjects = ImmutableSet.<String>builder().add(
            "finalize",
            "wait",
            "notifyAll",
            "notify",
            "toString",
            "clone",
            "equals",
            "hashCode",
            "getClass",
            "registerNatives").build();

    // ================================================================
    // Fields
    // ================================================================

    /**
     */
    private Filer filer;
    private DaoGenerateHelper daoGenerateHelper;

    // ================================================================
    // Constructors
    // ================================================================

    // ================================================================
    // Methods from/for super Interfaces or SuperClass
    // ================================================================

    /**
     * Initializes the processor with the processing environment
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        Trees.instance(processingEnv);
        processingEnv.getMessager();
        this.filer = processingEnv.getFiler();
        Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        this.daoGenerateHelper = new DaoGenerateHelper(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        annotations.stream()
                .filter(typeElement -> typeElement.toString().equals(PATH))
                .forEach(typeElement -> roundEnv.getElementsAnnotatedWith(typeElement)
                        .forEach((this::handle)));
        return true;
    }

    // ================================================================
    // Public or Protected Methods
    // ================================================================

    /**
     * 处理类
     */
    public void handle(Element element) {
        if (!(element.getKind() == ElementKind.INTERFACE))
            return;
        Symbol.ClassSymbol classSymbol = (Symbol.ClassSymbol) element;
        String qualifiedName = classSymbol.getQualifiedName().toString();
        String clz = classSymbol.getSimpleName().toString();
        String pkg = qualifiedName.substring(0, qualifiedName.lastIndexOf("."));
        this.genXmlConfig(pkg, clz, classSymbol);
    }

    // ================================================================
    // Getter & Setter
    // ================================================================

    // ================================================================
    // Private Methods
    // ================================================================

    /**
     * 获取xml配置
     */
    private void genXmlConfig(String pkg, String clz, Symbol.ClassSymbol classSymbol) {
        DaoGenerateHelper.doWithOriAndPrintWriter(filer,
                StandardLocation.CLASS_OUTPUT, pkg, clz + ".xml",
                (data, writer) -> {
                    String newXml = genNewXml(data, classSymbol);
                    writer.print(newXml);
                    writer.flush();
                }
        );
    }

    /**
     * 生成新的xml配置
     */
    private String genNewXml(String data, Symbol.ClassSymbol classSymbol) {
        DaoAutoGenerate daoGen = classSymbol.getAnnotation(DaoAutoGenerate.class);
        DaoEnvironment daoEnv = new DaoEnvironment(daoGen, classSymbol);
        Function<Symbol.MethodSymbol, MapperMethod> gen = (methodSymbol -> DaoGenerateHelper.toMapperMethod(daoEnv, methodSymbol));

        Map<String, MapperMethod> methodMap =
                daoGenerateHelper.getMember(Symbol.MethodSymbol.class, ElementKind.METHOD, classSymbol)
                        .stream()
                        .filter(methodSymbol1 -> !methodsInObjects.contains(methodSymbol1.getSimpleName().toString()))
                        .filter(m -> m.getAnnotationMirrors()
                                .stream()
                                .noneMatch(c -> c.getAnnotationType().toString().contains("org.apache.ibatis.annotations")))
                        .collect(Collectors.toMap(DaoGenerateHelper::getMethodName, gen));
        return daoGenerateHelper.mixMethodToData(daoGen, classSymbol.toString(), methodMap, data);
    }

    // ================================================================
    // Inner or Anonymous Class
    // ================================================================

    // ================================================================
    // Test Methods
    // ================================================================

}
